Add logging queue and reformat Engine function names

This commit is contained in:
Paolo Di Lorenzo
2025-06-11 14:21:14 -04:00
parent 3978e5252a
commit 733fd49daa
3 changed files with 66 additions and 40 deletions
+7
View File
@@ -1,3 +1,10 @@
# [unreleased]
### Improvements
* Commands and responses are now logged using their own synchronous queue to prevent unreadable output when commands and responses were sent simultaneously.
* `setLoggingEnabled(loggingEnabled:)` has been renamed `set(loggingEnabled:)`
* The original function remains but is deprecated.
# ChessKitEngine 0.6.0
Released Friday, May 30, 2025.
+58 -39
View File
@@ -7,17 +7,17 @@ import ChessKitEngineCore
public final class Engine: Sendable {
// MARK: - Public properties
// MARK: Public properties
/// The type of the engine.
public let type: EngineType
/// Whether the engine is currently running.
///
/// - To start the engine, call ``start(coreCount:multipv:)``.
/// - To stop the engine, call ``stop()``.
/// - To start the engine, call ``Engine/start(coreCount:multipv:)``.
/// - To stop the engine, call ``Engine/stop()``.
///
/// Engine must be running for ``send(command:)`` to work.
/// Engine must be running for ``Engine/send(command:)`` to work.
public var isRunning: Bool {
get async { await engineConfigurationActor.isRunning }
}
@@ -28,7 +28,7 @@ public final class Engine: Sendable {
/// will be logged to the console. The default value is
/// `false`.
///
/// Can be set via ``setLoggingEnabled(_:)`` function.
/// Can be set via ``Engine/set(loggingEnabled:)`` function.
public var loggingEnabled: Bool {
get async { await engineConfigurationActor.loggingEnabled }
}
@@ -41,7 +41,7 @@ public final class Engine: Sendable {
get async { await engineConfigurationActor.asyncStream }
}
// MARK: - Private properties
// MARK: Private properties
/// Actor used to hold mutating data in a thread safe environment.
private let engineConfigurationActor: EngineConfiguration
@@ -49,12 +49,19 @@ public final class Engine: Sendable {
/// Messenger used to communicate with engine.
private let messenger: EngineMessenger
private let queue = DispatchQueue(
label: "ck-engine-queue",
/// Queue used to synchronously send commands to engine.
private let commandQueue = DispatchQueue(
label: "ck-engine-command-queue",
qos: .userInteractive
)
// MARK: - Life cycle
/// Queue used to synchronously log engine commands and responses.
private let logQueue = DispatchQueue(
label: "ck-engine-log-queue",
qos: .utility
)
// MARK: Life cycle
/// Initializes an engine with the provided ``EngineType`` and optional logging enabled flag.
///
@@ -63,18 +70,18 @@ public final class Engine: Sendable {
/// will be logged to the console. The default value is `false`.
public init(type: EngineType, loggingEnabled: Bool = false) {
self.type = type
self.messenger = EngineMessenger(engineType: type.objc)
self.engineConfigurationActor = EngineConfiguration(loggingEnabled: loggingEnabled)
messenger = EngineMessenger(engineType: type.objc)
engineConfigurationActor = EngineConfiguration(loggingEnabled: loggingEnabled)
}
// This no longer work in an async environment as stop function outlives the deinit function.
// Support for async deinit should be added in a future version of Swift (6.2)
// https://github.com/swiftlang/swift-evolution/blob/main/proposals/0371-isolated-synchronous-deinit.md
// deinit {
// stop()
// }
// deinit {
// stop()
// }
// MARK: - Public functions
// MARK: Public functions
/// Starts the engine.
///
@@ -104,10 +111,10 @@ public final class Engine: Sendable {
/// Stops the engine.
///
/// Call this to stop all engine calculation and clean up.
/// After calling ``stop()``, ``start(coreCount:multipv:)`` must be called before
/// sending any more commands with ``send(command:)``.
/// After calling ``Engine/stop()``, ``Engine/start(coreCount:multipv:)`` must be called before
/// sending any more commands with ``Engine/send(command:)``.
///
/// - note: as temporary fix this function must be called before deiniting the engine.
/// - note: As a temporary fix this function must be called before deinitializing the engine.
public func stop() async {
guard await isRunning == true else { return }
@@ -116,8 +123,8 @@ public final class Engine: Sendable {
messenger.stop()
await engineConfigurationActor.clearAsyncStream()
await engineConfigurationActor.setIsRunning(isRunning: false)
await engineConfigurationActor.setInitialSetupComplete(initialSetupComplete: false)
await engineConfigurationActor.set(isRunning: false)
await engineConfigurationActor.set(initialSetupComplete: false)
}
/// Sends a command to the engine.
@@ -127,7 +134,7 @@ public final class Engine: Sendable {
/// Commands must be of type ``EngineCommand`` to ensure
/// validity.
///
/// Any responses will be returned via ``responseStream``.
/// Any responses will be returned via ``Engine/responseStream``.
public func send(command: EngineCommand) async {
guard await isRunning || [.uci, .isready].contains(command) else {
await log("Engine is not running, call start() first.")
@@ -136,7 +143,7 @@ public final class Engine: Sendable {
await log(command.rawValue)
queue.sync {
commandQueue.sync {
messenger.sendCommand(command.rawValue)
}
}
@@ -146,19 +153,30 @@ public final class Engine: Sendable {
/// - parameter loggingEnabled: If set to `true`, engine commands and responses
/// will be logged to the console. The default value is `false`.
///
@available(*, deprecated, renamed: "set(loggingEnabled:)")
public func setLoggingEnabled(_ loggingEnabled: Bool) {
set(loggingEnabled: loggingEnabled)
}
/// Enable printing logs to console.
///
/// - parameter loggingEnabled: If set to `true`, engine commands and responses
/// will be logged to the console. The default value is `false`.
///
public func set(loggingEnabled: Bool) {
Task {
await engineConfigurationActor
.setLoggingEnabled(loggingEnabled: loggingEnabled)
await engineConfigurationActor.set(loggingEnabled: loggingEnabled)
}
}
// MARK: - Private functions
// MARK: Private functions
/// Logs `message` if `loggingEnabled` is `true`.
/// Logs `message` if ``Engine/loggingEnabled`` is `true`.
private func log(_ message: String) async {
if await loggingEnabled {
Logging.print(message)
logQueue.sync {
Logging.print(message)
}
}
}
@@ -201,7 +219,7 @@ public final class Engine: Sendable {
private func performInitialSetup(coreCount: Int, multipv: Int) async {
guard await !engineConfigurationActor.initialSetupComplete else { return }
await engineConfigurationActor.setIsRunning(isRunning: true)
await engineConfigurationActor.set(isRunning: true)
// configure engine-specific options
for command in type.setupCommands {
@@ -214,10 +232,9 @@ public final class Engine: Sendable {
id: "Threads",
value: "\(max(coreCount - 1, 1))"
))
await send(command: .setoption(id: "MultiPV", value: "\(multipv)"))
await engineConfigurationActor
.setInitialSetupComplete(initialSetupComplete: true)
await send(command: .setoption(id: "MultiPV", value: "\(multipv)"))
await engineConfigurationActor.set(initialSetupComplete: true)
}
}
@@ -225,7 +242,7 @@ public final class Engine: Sendable {
// MARK: - EngineConfiguration actor
/// An actor to hold the configuration for the engine class.
/// Since `Engine` conforms to `Sendable` protocol, we need to
/// Since ``Engine`` conforms to `Sendable` protocol, we need to
/// move the mutable data into async safe environment.
fileprivate actor EngineConfiguration: Sendable {
/// Whether the engine is currently running.
@@ -249,23 +266,25 @@ fileprivate actor EngineConfiguration: Sendable {
Task { await setAsyncStream() }
}
func setLoggingEnabled(loggingEnabled: Bool) async {
func set(loggingEnabled: Bool) async {
self.loggingEnabled = loggingEnabled
}
func setInitialSetupComplete(initialSetupComplete: Bool) async {
func set(initialSetupComplete: Bool) async {
self.initialSetupComplete = initialSetupComplete
}
func setIsRunning(isRunning: Bool) async {
func set(isRunning: Bool) async {
self.isRunning = isRunning
}
func setAsyncStream() async {
guard asyncStream == nil else { return }
asyncStream = AsyncStream { (continuation: AsyncStream<EngineResponse>.Continuation) -> Void in
Task { await setStreamContinuation(continuation) }
asyncStream = AsyncStream { continuation in
Task {
await set(streamContinuation: continuation)
}
}
}
@@ -274,7 +293,7 @@ fileprivate actor EngineConfiguration: Sendable {
streamContinuation = nil
}
private func setStreamContinuation(_ continuation: AsyncStream<EngineResponse>.Continuation?) async {
self.streamContinuation = continuation
private func set(streamContinuation: AsyncStream<EngineResponse>.Continuation?) async {
self.streamContinuation = streamContinuation
}
}
@@ -73,7 +73,7 @@ NSLock *_lock;
dup2([[_writePipe fileHandleForReading] fileDescriptor], fileno(stdin));
// create command dispatch queue and start engine
_queue = dispatch_queue_create("ck-message-queue", DISPATCH_QUEUE_CONCURRENT);
_queue = dispatch_queue_create("ck-engine-response-queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(_queue, ^{
_engine->initialize();