mirror of
https://github.com/OpenEmu/OpenEmuKit.git
synced 2025-11-01 11:08:14 +00:00
165 lines
5.8 KiB
Swift
165 lines
5.8 KiB
Swift
// Copyright (c) 2022, OpenEmu Team
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
// * Neither the name of the OpenEmu Team nor the
|
|
// names of its contributors may be used to endorse or promote products
|
|
// derived from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
// DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
|
|
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import Foundation
|
|
import OpenEmuKitPrivate
|
|
|
|
public enum ShaderPresetWriteError: Error {
|
|
case invalidCharacters
|
|
case missingCreatedAt
|
|
}
|
|
|
|
@frozen public enum ShaderPresetTextWriter {
|
|
@frozen public struct Options: OptionSet {
|
|
public let rawValue: Int
|
|
|
|
public init(rawValue: Int) {
|
|
self.rawValue = rawValue
|
|
}
|
|
|
|
public static let name = Self(rawValue: 1 << 0)
|
|
public static let shader = Self(rawValue: 1 << 1)
|
|
public static let createdAt = Self(rawValue: 1 << 2)
|
|
|
|
public static let all: Self = [.name, .shader]
|
|
}
|
|
|
|
static var formatter: NumberFormatter {
|
|
let formatter = NumberFormatter()
|
|
formatter.numberStyle = .decimal
|
|
formatter.maximumFractionDigits = 4
|
|
formatter.decimalSeparator = "."
|
|
formatter.groupingSeparator = ""
|
|
return formatter
|
|
}
|
|
|
|
static let invalidCharacters = #"#""#
|
|
static let invalidCharacterSet = CharacterSet(charactersIn: invalidCharacters)
|
|
|
|
/// A boolean value to determine if a string is a valid identifier.
|
|
/// - Parameter s: The string to be validated.
|
|
/// - Returns: `true` if s is a valid identifier.
|
|
public static func isValidIdentifier(_ s: String) -> Bool {
|
|
s.rangeOfCharacter(from: invalidCharacterSet) == nil
|
|
}
|
|
|
|
public static func write(preset c: ShaderPresetData, options: Options = [.shader]) throws -> String {
|
|
var s = ""
|
|
|
|
var first = true
|
|
if options.contains(.name) {
|
|
guard isValidIdentifier(c.name) else { throw ShaderPresetWriteError.invalidCharacters }
|
|
first = false
|
|
s.append("$name=\"\(c.name)\"")
|
|
}
|
|
|
|
if options.contains(.shader) {
|
|
guard isValidIdentifier(c.shader) else { throw ShaderPresetWriteError.invalidCharacters }
|
|
if !first {
|
|
s.append(";")
|
|
} else {
|
|
first = false
|
|
}
|
|
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 {
|
|
s.append(";")
|
|
}
|
|
s.append("\(key)=\(formatter.string(from: c.parameters[key]! as NSNumber)!)")
|
|
first = false
|
|
}
|
|
|
|
return s
|
|
}
|
|
}
|
|
|
|
public enum ShaderPresetReadError: Error {
|
|
/// Preset is malformed
|
|
case malformed
|
|
}
|
|
|
|
@frozen public enum ShaderPresetTextReader {
|
|
enum State {
|
|
case key, value
|
|
}
|
|
|
|
public static func read(text: String, id: String? = nil) throws -> ShaderPresetData {
|
|
guard let tokens = try? PKVScanner.parse(text: text)
|
|
else { throw ShaderPresetReadError.malformed }
|
|
|
|
var name = "Unnamed shader preset"
|
|
var shader = ""
|
|
var createdAt: TimeInterval?
|
|
var params = [String: Double]()
|
|
|
|
var iter = tokens.makePeekableIterator()
|
|
while let (tok, key) = iter.peek(), tok == .identifier || tok == .systemIdentifier {
|
|
iter.next()
|
|
|
|
if tok == .identifier {
|
|
guard
|
|
let (tok, val) = iter.peek(),
|
|
tok == .float
|
|
else { break }
|
|
iter.next()
|
|
params[key] = Double(val)
|
|
continue
|
|
}
|
|
|
|
// tok == .reserveIdentifier
|
|
guard
|
|
let (tok, val) = iter.peek(),
|
|
tok == .string
|
|
else { break }
|
|
iter.next()
|
|
|
|
switch key {
|
|
case "$name":
|
|
name = val
|
|
case "$shader":
|
|
shader = val
|
|
case "$createdAt":
|
|
createdAt = TimeInterval(val)
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
return ShaderPresetData(name: name, shader: shader, parameters: params, id: id, createdAt: createdAt)
|
|
}
|
|
}
|