mirror of
https://github.com/OpenEmu/OpenEmuKit.git
synced 2025-11-01 11:08:14 +00:00
fix: Remove unique name requirement for shader presets
This commit is contained in:
@@ -81,13 +81,10 @@ class ShaderPresetModelTests: XCTestCase {
|
||||
func testCanFindPreset() {
|
||||
expect(self.presets.findPreset(byID: "id1"))
|
||||
.toNot(be(nil))
|
||||
expect(self.presets.findPreset(byName: "shader 2"))
|
||||
.toNot(be(nil))
|
||||
}
|
||||
|
||||
func testInstancesAreSame() {
|
||||
expect(self.presets.findPreset(byID: "id2")) === presets.findPreset(byID: "id2")
|
||||
expect(self.presets.findPreset(byName: "shader 1")) === presets.findPreset(byName: "shader 1")
|
||||
}
|
||||
|
||||
func testFindPresets() {
|
||||
@@ -101,9 +98,6 @@ class ShaderPresetModelTests: XCTestCase {
|
||||
func testExists() {
|
||||
expect(self.presets.exists(byID: "id2")) == true
|
||||
expect(self.presets.exists(byID: "foo")) == false
|
||||
|
||||
expect(self.presets.exists(byName: "shader 1")) == true
|
||||
expect(self.presets.exists(byName: "foo")) == false
|
||||
}
|
||||
|
||||
func testRemovePreset() {
|
||||
@@ -121,10 +115,10 @@ class ShaderPresetModelTests: XCTestCase {
|
||||
XCTFail("Expected to find id2")
|
||||
return
|
||||
}
|
||||
let oldName = a.name
|
||||
a.name = "dummy name"
|
||||
try presets.savePreset(a)
|
||||
expect(self.presets.findPreset(byName: oldName)).to(beNil())
|
||||
expect(self.presets.findPreset(byName: "dummy name")).toNot(beNil())
|
||||
guard let b = presets.findPreset(byID: "id2")
|
||||
else { return XCTFail("Expected to find id2") }
|
||||
expect(b.name).to(equal("dummy name"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,34 +79,6 @@ class UserDefaultsPresetStorageTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
func testFailsForDuplicateName() {
|
||||
let store = store!
|
||||
expect {
|
||||
try store.save(ShaderPresetData(name: "foo", shader: "CRT", parameters: [:], id: "id1"))
|
||||
try store.save(ShaderPresetData(name: "foo", shader: "MAME", parameters: [:], id: "id2"))
|
||||
}
|
||||
.to(throwError(ShaderPresetStorageError.duplicateName))
|
||||
}
|
||||
|
||||
func testRenameShaderSucceeds() throws {
|
||||
let store = store!
|
||||
try store.save(ShaderPresetData(name: "shader a", shader: "CRT", parameters: [:], id: "id1"))
|
||||
try store.save(ShaderPresetData(name: "shader b", shader: "CRT", parameters: [:], id: "id1"))
|
||||
expect(store.findPreset(byName: "shader a")).to(beNil())
|
||||
expect(store.findPreset(byName: "shader b")).toNot(beNil())
|
||||
}
|
||||
|
||||
func testRenameShaderFailsForDuplicateName() throws {
|
||||
let store = store!
|
||||
try store.save(ShaderPresetData(name: "shader a", shader: "CRT", parameters: [:], id: "id1"))
|
||||
try store.save(ShaderPresetData(name: "shader b", shader: "CRT", parameters: [:], id: "id2"))
|
||||
|
||||
expect {
|
||||
try store.save(ShaderPresetData(name: "shader b", shader: "CRT", parameters: [:], id: "id1"))
|
||||
}
|
||||
.to(throwError(ShaderPresetStorageError.duplicateName))
|
||||
}
|
||||
|
||||
func testFailsForModifiedShader() {
|
||||
let store = store!
|
||||
expect {
|
||||
|
||||
@@ -29,23 +29,36 @@ import Foundation
|
||||
public var name: String
|
||||
public let shader: OEShaderModel
|
||||
public var parameters: [String: Double]
|
||||
public let createdAt: Date
|
||||
|
||||
public init(name: String, shader: OEShaderModel, parameters: [String: Double]? = nil, id: String? = nil) {
|
||||
public init(name: String, shader: OEShaderModel, parameters: [String: Double]? = nil, id: String? = nil, createdAt: Date = Date()) {
|
||||
// generate an ID that is useful for us humans, but still unique enough
|
||||
// that a client won't generate duplicates.
|
||||
self.id = id ?? "\(shader.name):\(UInt(Date().timeIntervalSince1970))"
|
||||
self.name = name
|
||||
self.shader = shader
|
||||
self.parameters = parameters ?? Dictionary(allParams: shader.defaultParameters)
|
||||
self.createdAt = createdAt
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
extension ShaderPreset: Comparable {
|
||||
public static func < (lhs: ShaderPreset, rhs: ShaderPreset) -> Bool {
|
||||
let res = lhs.name.localizedCompare(rhs.name)
|
||||
if res == .orderedSame {
|
||||
return lhs.createdAt < rhs.createdAt
|
||||
}
|
||||
return res == .orderedAscending
|
||||
}
|
||||
}
|
||||
|
||||
extension ShaderPresetData {
|
||||
public init(preset: ShaderPreset) {
|
||||
id = preset.id
|
||||
name = preset.name
|
||||
shader = preset.shader.name
|
||||
parameters = preset.parameters
|
||||
createdAt = preset.createdAt.timeIntervalSince1970
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,13 @@ public struct ShaderPresetData: Identifiable, Hashable, Codable {
|
||||
public let name: String
|
||||
public let shader: String
|
||||
public var parameters: [String: Double]
|
||||
public let createdAt: TimeInterval?
|
||||
|
||||
public init(name: String, shader: String, parameters: [String: Double], id: String? = nil) {
|
||||
public init(name: String, shader: String, parameters: [String: Double], id: String? = nil, createdAt: TimeInterval? = nil) {
|
||||
self.id = id ?? name
|
||||
self.name = name
|
||||
self.shader = shader
|
||||
self.parameters = parameters
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,6 @@
|
||||
import Foundation
|
||||
|
||||
public enum ShaderPresetStorageError: Error {
|
||||
/// A preset with the same name already exists
|
||||
case duplicateName
|
||||
|
||||
/// The preset shader was changed
|
||||
case shaderModified
|
||||
|
||||
@@ -42,17 +39,11 @@ public protocol ShaderPresetStorage {
|
||||
/// - Returns: An array of ``ShaderPresetData`` objects.
|
||||
func findPresets(byShader name: String) -> [ShaderPresetData]
|
||||
|
||||
/// Returns the shader preset for the specified preset name.
|
||||
/// - Parameter name: The name of the preset to find.
|
||||
/// - Returns: A preset matching the specified name.
|
||||
func findPreset(byName name: String) -> ShaderPresetData?
|
||||
|
||||
/// Returns the shader preset for the specified identifier.
|
||||
/// - Parameter id: The identifier of the preset to find.
|
||||
/// - Returns: A preset matching the specified identifier.
|
||||
func findPreset(byID id: String) -> ShaderPresetData?
|
||||
func save(_ preset: ShaderPresetData) throws
|
||||
func remove(_ preset: ShaderPresetData)
|
||||
func exists(byName name: String) -> Bool
|
||||
func exists(byID id: String) -> Bool
|
||||
}
|
||||
|
||||
@@ -45,10 +45,6 @@ public class ShaderPresetStore {
|
||||
|
||||
// Indices
|
||||
var byID = [String: ShaderPreset]() // map id → preset
|
||||
var byName = [String: String]() // map name → id
|
||||
|
||||
/// tracks the persisted name of the shader
|
||||
var idToName = [String: String]()
|
||||
|
||||
public init(store: ShaderPresetStorage, shaders: OEShaderStore) {
|
||||
self.store = store
|
||||
@@ -87,12 +83,6 @@ public class ShaderPresetStore {
|
||||
if self.byID[data.id] == nil {
|
||||
// new item
|
||||
self.byID[data.id] = preset
|
||||
self.byName[data.name] = data.id
|
||||
self.idToName[data.id] = data.name
|
||||
} else if let name = self.idToName[data.id], name != data.name {
|
||||
self.byName.removeValue(forKey: name)
|
||||
self.byName[data.name] = data.id
|
||||
self.idToName[data.id] = data.name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,9 +92,6 @@ public class ShaderPresetStore {
|
||||
store.remove(data)
|
||||
queue.async(flags: .barrier) {
|
||||
self.byID.removeValue(forKey: data.id)
|
||||
if let name = self.idToName.removeValue(forKey: data.id) {
|
||||
self.byName.removeValue(forKey: name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,33 +109,12 @@ public class ShaderPresetStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the shader preset matching the specified name.
|
||||
///
|
||||
/// - Note:
|
||||
/// This function returns `nil` if a preset is found
|
||||
/// but no valid shader is installed.
|
||||
///
|
||||
/// - Parameter name: The name of the preset to locate.
|
||||
/// - Returns: A matching preset or `nil`.
|
||||
public func findPreset(byName name: String) -> ShaderPreset? {
|
||||
queue.sync {
|
||||
getPreset(byName: name)
|
||||
}
|
||||
}
|
||||
|
||||
public func findPreset(byID id: String) -> ShaderPreset? {
|
||||
queue.sync {
|
||||
getPreset(byID: id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines if a preset exists with the specified name.
|
||||
/// - Parameter name: The unique name of the preset to search for.
|
||||
/// - Returns: `true` if a preset exists.
|
||||
public func exists(byName name: String) -> Bool {
|
||||
store.exists(byName: name)
|
||||
}
|
||||
|
||||
/// Determines if a preset exists with the specified id.
|
||||
/// - Parameter id: The unique identifier of the preset to search for.
|
||||
/// - Returns: `true` if a preset exists.
|
||||
@@ -175,24 +141,10 @@ public class ShaderPresetStore {
|
||||
return makePreset(data: data, shader: shader)
|
||||
}
|
||||
|
||||
private func getPreset(byName name: String) -> ShaderPreset? {
|
||||
if let id = byName[name] {
|
||||
return getPreset(byID: id)
|
||||
}
|
||||
|
||||
guard
|
||||
let data = store.findPreset(byName: name),
|
||||
let shader = shaders[data.shader]
|
||||
else { return nil }
|
||||
|
||||
return makePreset(data: data, shader: shader)
|
||||
}
|
||||
|
||||
private func makePreset(data: ShaderPresetData, shader: OEShaderModel) -> ShaderPreset {
|
||||
let preset = ShaderPreset(name: data.name, shader: shader, parameters: data.parameters, id: data.id)
|
||||
byID[data.id] = preset
|
||||
byName[data.name] = preset.id
|
||||
idToName[data.id] = preset.name
|
||||
let createdAt = data.createdAt != nil ? Date(timeIntervalSince1970: data.createdAt!) : Date()
|
||||
let preset = ShaderPreset(name: data.name, shader: shader, parameters: data.parameters, id: data.id, createdAt: createdAt)
|
||||
byID[data.id] = preset
|
||||
return preset
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import OpenEmuKitPrivate
|
||||
|
||||
public enum ShaderPresetWriteError: Error {
|
||||
case invalidCharacters
|
||||
case missingCreatedAt
|
||||
}
|
||||
|
||||
@frozen public struct ShaderPresetTextWriter {
|
||||
@@ -39,7 +40,8 @@ public enum ShaderPresetWriteError: Error {
|
||||
|
||||
public static let name = Self(rawValue: 1 << 0)
|
||||
public static let shader = Self(rawValue: 1 << 1)
|
||||
public static let sign = Self(rawValue: 1 << 2)
|
||||
public static let createdAt = Self(rawValue: 1 << 2)
|
||||
public static let sign = Self(rawValue: 1 << 3)
|
||||
|
||||
public static let all: Self = [.name, .shader]
|
||||
}
|
||||
@@ -85,6 +87,16 @@ public enum ShaderPresetWriteError: Error {
|
||||
s.append("$shader=\"\(c.shader)\"")
|
||||
}
|
||||
|
||||
if options.contains(.createdAt) {
|
||||
guard let createdAt = c.createdAt else { throw ShaderPresetWriteError.missingCreatedAt }
|
||||
if !first {
|
||||
s.append(";")
|
||||
} else {
|
||||
first = false
|
||||
}
|
||||
s.append("$createdAt=\"\(UInt64(createdAt))\"")
|
||||
}
|
||||
|
||||
// Sort the keys for a consistent output
|
||||
for key in c.parameters.keys.sorted() {
|
||||
if !first {
|
||||
@@ -140,8 +152,9 @@ public enum ShaderPresetReadError: Error {
|
||||
guard let tokens = try? PKVScanner.parse(text: text[..<paramsEnd])
|
||||
else { throw ShaderPresetReadError.malformed }
|
||||
|
||||
var name: String?
|
||||
var shader: String?
|
||||
var name = "Unnamed shader preset"
|
||||
var shader = ""
|
||||
var createdAt: TimeInterval?
|
||||
var params = [String: Double]()
|
||||
|
||||
var iter = tokens.makePeekableIterator()
|
||||
@@ -170,21 +183,13 @@ public enum ShaderPresetReadError: Error {
|
||||
name = val
|
||||
case "$shader":
|
||||
shader = val
|
||||
case "$createdAt":
|
||||
createdAt = TimeInterval(val)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch (name, shader) {
|
||||
case (.none, .none):
|
||||
return ShaderPresetData(name: "Unnamed shader preset", shader: "", parameters: params, id: id)
|
||||
case (.none, .some(let shader)):
|
||||
return ShaderPresetData(name: "Unnamed shader preset", shader: shader, parameters: params, id: id)
|
||||
case (.some(let name), .some(let shader)):
|
||||
return ShaderPresetData(name: name, shader: shader, parameters: params, id: id)
|
||||
default:
|
||||
throw ShaderPresetReadError.malformed
|
||||
}
|
||||
return ShaderPresetData(name: name, shader: shader, parameters: params, id: id, createdAt: createdAt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ public class UserDefaultsPresetStorage: ShaderPresetStorage {
|
||||
let queue: DispatchQueue = DispatchQueue(label: "org.openemu.userDefaultsPresetStore", attributes: .concurrent,
|
||||
target: DispatchQueue.global(qos: .userInitiated))
|
||||
|
||||
var indexByName: [String: String] = [:]
|
||||
var indexByShader: [String: [String]] = [:]
|
||||
|
||||
public init(store: UserDefaults) {
|
||||
@@ -40,11 +39,9 @@ public class UserDefaultsPresetStorage: ShaderPresetStorage {
|
||||
// populate indices
|
||||
queue.async(flags: .barrier) {
|
||||
// Collect all the shader preset keys
|
||||
let keys = store.dictionaryRepresentation().keys.filter { $0.hasPrefix(Self.presetPrefix) }
|
||||
let keys = store.dictionaryRepresentation().keys.filter { $0.hasPrefix(Self.presetPrefix) }
|
||||
let presets = keys.compactMap(self.load(_:))
|
||||
|
||||
self.indexByName = Dictionary(uniqueKeysWithValues: presets.map { ($0.name, $0.id) })
|
||||
|
||||
// Groups presets by shader and then remaps the values from [ShaderPresetData] → [\.id]
|
||||
self.indexByShader = Dictionary(grouping: presets, by: { $0.shader })
|
||||
.mapValues { $0.map(\.id) }
|
||||
@@ -55,37 +52,18 @@ public class UserDefaultsPresetStorage: ShaderPresetStorage {
|
||||
id.hasPrefix(Self.presetPrefix) ? id : "\(Self.presetPrefix)\(id)"
|
||||
}
|
||||
|
||||
public func exists(byName name: String) -> Bool {
|
||||
queue.sync {
|
||||
indexByName[name] != nil
|
||||
}
|
||||
}
|
||||
|
||||
public func exists(byID id: String) -> Bool {
|
||||
queue.sync {
|
||||
store.string(forKey: Self.makeKey(id)) != nil
|
||||
}
|
||||
}
|
||||
|
||||
public func presets(matching predicate: (ShaderPresetData) -> Bool) -> [ShaderPresetData] {
|
||||
queue.sync {
|
||||
indexByName.values.compactMap(load(_:)).filter(predicate)
|
||||
}
|
||||
}
|
||||
|
||||
public func findPresets(byShader name: String) -> [ShaderPresetData] {
|
||||
queue.sync {
|
||||
indexByShader[name]?.compactMap(load(_:)) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
public func findPreset(byName name: String) -> ShaderPresetData? {
|
||||
queue.sync {
|
||||
guard let id = indexByName[name] else { return nil }
|
||||
return load(id)
|
||||
}
|
||||
}
|
||||
|
||||
public func findPreset(byID id: String) -> ShaderPresetData? {
|
||||
queue.sync {
|
||||
load(id)
|
||||
@@ -94,10 +72,6 @@ public class UserDefaultsPresetStorage: ShaderPresetStorage {
|
||||
|
||||
public func save(_ preset: ShaderPresetData) throws {
|
||||
try queue.sync(flags: .barrier) {
|
||||
if let existing = indexByName[preset.name], existing != preset.id {
|
||||
throw ShaderPresetStorageError.duplicateName
|
||||
}
|
||||
|
||||
let existing = load(preset.id)
|
||||
|
||||
if let existing = existing, existing.shader != preset.shader {
|
||||
@@ -105,21 +79,18 @@ public class UserDefaultsPresetStorage: ShaderPresetStorage {
|
||||
throw ShaderPresetStorageError.shaderModified
|
||||
}
|
||||
|
||||
// Ensure created at is set
|
||||
let createdAt = existing?.createdAt ?? preset.createdAt ?? Date().timeIntervalSince1970
|
||||
let preset = ShaderPresetData(name: preset.name, shader: preset.shader, parameters: preset.parameters, id: preset.id, createdAt: createdAt)
|
||||
|
||||
do {
|
||||
let text = try ShaderPresetTextWriter().write(preset: preset, options: [.name, .shader])
|
||||
let text = try ShaderPresetTextWriter().write(preset: preset, options: [.name, .shader, .createdAt])
|
||||
store.set(text, forKey: Self.makeKey(preset.id))
|
||||
|
||||
//
|
||||
// Update indices
|
||||
//
|
||||
if let existing = existing {
|
||||
if existing.name != preset.name {
|
||||
// Preset was renamed
|
||||
indexByName[existing.name] = nil
|
||||
indexByName[preset.name] = preset.id
|
||||
}
|
||||
} else {
|
||||
indexByName[preset.name] = preset.id
|
||||
if existing == nil {
|
||||
var idsByShader = indexByShader[preset.shader] ?? []
|
||||
idsByShader.append(preset.id)
|
||||
indexByShader[preset.shader] = idsByShader
|
||||
@@ -139,7 +110,6 @@ public class UserDefaultsPresetStorage: ShaderPresetStorage {
|
||||
//
|
||||
// Update indices
|
||||
//
|
||||
indexByName.removeValue(forKey: existing.name)
|
||||
if
|
||||
var idsByShader = indexByShader[existing.shader],
|
||||
let index = idsByShader.firstIndex(of: existing.id)
|
||||
|
||||
Reference in New Issue
Block a user