fix: Remove unique name requirement for shader presets

This commit is contained in:
Stuart Carnie
2022-02-06 08:45:23 +11:00
parent 64c0eb7ba7
commit 6efeb2fadb
8 changed files with 49 additions and 150 deletions
+3 -9
View File
@@ -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 {
+14 -1
View File
@@ -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
}
}
+3 -1
View File
@@ -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
}
}
-9
View File
@@ -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
}
+3 -51
View File
@@ -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
}
}
+19 -14
View File
@@ -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)
}
}
+7 -37
View File
@@ -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)