mirror of
https://github.com/chesskit-app/chesskit-engine.git
synced 2026-05-19 15:50:35 +00:00
Improve formatting and update readme
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
# [unreleased]
|
||||
|
||||
### Breaking Changes
|
||||
* `ChessKitEngine` now supports Swift 6 concurrency.
|
||||
* Starting and stopping engine, as well as sending commands must now be done using `async`/`await`.
|
||||
|
||||
# ChessKitEngine 0.5.0
|
||||
Released Tuesday, September 10, 2024.
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import ChessKitEngineCore
|
||||
|
||||
public final class Engine: Sendable {
|
||||
|
||||
//MARK: - Public properties
|
||||
|
||||
// MARK: - Public properties
|
||||
|
||||
/// The type of the engine.
|
||||
public let type: EngineType
|
||||
|
||||
@@ -41,9 +41,9 @@ public final class Engine: Sendable {
|
||||
get async { await engineConfigurationActor.asyncStream }
|
||||
}
|
||||
|
||||
//MARK: - Private properties
|
||||
|
||||
///Actor used to hold mutating data in a thread safe environment.
|
||||
// MARK: - Private properties
|
||||
|
||||
/// Actor used to hold mutating data in a thread safe environment.
|
||||
private let engineConfigurationActor: EngineConfiguration
|
||||
|
||||
/// Messenger used to communicate with engine.
|
||||
@@ -54,8 +54,8 @@ public final class Engine: Sendable {
|
||||
qos: .userInteractive
|
||||
)
|
||||
|
||||
//MARK: - Life cycle functions
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
/// Initializes an engine with the provided ``EngineType`` and optional logging enabled flag.
|
||||
///
|
||||
/// - parameter type: The type of engine to use.
|
||||
@@ -69,14 +69,14 @@ public final class Engine: Sendable {
|
||||
|
||||
|
||||
// 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.1)
|
||||
// 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()
|
||||
// }
|
||||
|
||||
//MARK: - Public functions
|
||||
|
||||
// MARK: - Public functions
|
||||
|
||||
/// Starts the engine.
|
||||
///
|
||||
/// You must call this function and wait for ``EngineResponse/readyok``
|
||||
@@ -92,7 +92,7 @@ public final class Engine: Sendable {
|
||||
coreCount: Int? = nil,
|
||||
multipv: Int = 1
|
||||
) async {
|
||||
//Setup async stream response if not already set.
|
||||
// Setup async stream response if not already set.
|
||||
await engineConfigurationActor.setAsyncStream()
|
||||
|
||||
setMessengerResponseHandler(coreCount: coreCount, multipv: multipv)
|
||||
@@ -222,12 +222,11 @@ public final class Engine: Sendable {
|
||||
|
||||
}
|
||||
|
||||
//MARK: EngineConfiguration actor
|
||||
// MARK: - EngineConfiguration actor
|
||||
|
||||
//An actor to hold the configuration for the engine class.
|
||||
//Since engine now conforms to sendable protocol, we need to
|
||||
//move the mutable data into async safe environment.
|
||||
//
|
||||
/// An actor to hold the configuration for the engine class.
|
||||
/// 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.
|
||||
private(set) var isRunning = false
|
||||
@@ -263,16 +262,16 @@ fileprivate actor EngineConfiguration: Sendable {
|
||||
}
|
||||
|
||||
func setAsyncStream() async {
|
||||
guard self.asyncStream == nil else { return }
|
||||
guard asyncStream == nil else { return }
|
||||
|
||||
self.asyncStream = AsyncStream { (continuation: AsyncStream<EngineResponse>.Continuation) -> Void in
|
||||
asyncStream = AsyncStream { (continuation: AsyncStream<EngineResponse>.Continuation) -> Void in
|
||||
Task{ await setStreamContinuation(continuation) }
|
||||
}
|
||||
}
|
||||
|
||||
func clearAsyncStream() async {
|
||||
self.asyncStream = nil
|
||||
self.streamContinuation = nil
|
||||
asyncStream = nil
|
||||
streamContinuation = nil
|
||||
}
|
||||
|
||||
private func setStreamContinuation(_ continuation: AsyncStream<EngineResponse>.Continuation?) async {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
/// Possible engine commands based on the
|
||||
/// [Universal Chess Interface (UCI)](https://backscattering.de/chess/uci/2006-04.txt).
|
||||
///
|
||||
public enum EngineCommand: Equatable, Sendable {
|
||||
|
||||
/// `"debug [ on | off ]"`
|
||||
@@ -166,7 +165,7 @@ public enum EngineCommand: Equatable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
internal static func nextSetupLoopCommand(given response: EngineResponse?) -> EngineCommand? {
|
||||
static func nextSetupLoopCommand(given response: EngineResponse?) -> EngineCommand? {
|
||||
// engine setup loop
|
||||
// <uci> → <uciok> → <isready> → <readok> → complete
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
/// Possible engine responses based on the
|
||||
/// [Universal Chess Interface (UCI)](https://backscattering.de/chess/uci/2006-04.txt).
|
||||
///
|
||||
public enum EngineResponse: Sendable {
|
||||
|
||||
/// `"id name <x>"`, `"id author <x>"`
|
||||
|
||||
@@ -60,10 +60,10 @@ NSLock *_lock;
|
||||
];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
//This has to run on a thread that has an active run loop
|
||||
//otherwise we don't get notified when a read occurs.
|
||||
//Since we are using async, the only active run loop we can
|
||||
//guarentee to have an active run loop is the main thread.
|
||||
// This has to run on a thread that has an active run loop
|
||||
// otherwise we don't get notified when a read occurs.
|
||||
// Since we are using async, the only active run loop we can
|
||||
// guarentee to have an active run loop is the main thread.
|
||||
[_pipeReadHandle readInBackgroundAndNotify];
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import XCTest
|
||||
///
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
@TestsActor
|
||||
class BaseEngineTests: XCTestCase {
|
||||
|
||||
@@ -58,7 +57,6 @@ class BaseEngineTests: XCTestCase {
|
||||
)
|
||||
|
||||
await startEngine(expectation: expectation)
|
||||
|
||||
await fulfillment(of: [expectation], timeout: 5)
|
||||
}
|
||||
|
||||
@@ -74,9 +72,7 @@ class BaseEngineTests: XCTestCase {
|
||||
)
|
||||
|
||||
await startEngine(expectation: expectationStartEngine)
|
||||
|
||||
await stopEngine(expectation: expectationStopEngine)
|
||||
|
||||
await fulfillment(of: [expectationStartEngine, expectationStopEngine], timeout: 5)
|
||||
}
|
||||
|
||||
@@ -96,12 +92,11 @@ class BaseEngineTests: XCTestCase {
|
||||
await startEngine(expectation: expectationStartEngine)
|
||||
await stopEngine(expectation: expectationStopEngine)
|
||||
await startEngine(expectation: expectationStartEngine)
|
||||
|
||||
await fulfillment(of: [expectationStartEngine, expectationStopEngine], timeout: 5)
|
||||
}
|
||||
|
||||
|
||||
internal func stopEngine(expectation: XCTestExpectation) async {
|
||||
func stopEngine(expectation: XCTestExpectation) async {
|
||||
await engine.stop()
|
||||
|
||||
if await !engine.isRunning,
|
||||
@@ -110,7 +105,7 @@ class BaseEngineTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
internal func startEngine(expectation: XCTestExpectation) async {
|
||||
func startEngine(expectation: XCTestExpectation) async {
|
||||
await engine.start()
|
||||
|
||||
for await response in await engine.responseStream! {
|
||||
@@ -131,12 +126,12 @@ class BaseEngineTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
//This actor's purpose is to ensure tests for the engine
|
||||
//class aren't running on main thread.
|
||||
//Since [EngineMessenger start] function now uses
|
||||
//`dispatch_async(dispatch_get_main_queue, (), ^{...});`
|
||||
//which is the main thread to listen for read notifications,
|
||||
//testing on main thread is counter productive.
|
||||
/// Ensures tests for the `Engine` class don't run on main thread.
|
||||
///
|
||||
/// Since `[EngineMessenger start]` function now uses
|
||||
/// `dispatch_async(dispatch_get_main_queue, (), ^{...});`
|
||||
/// which is the main thread to listen for read notifications,
|
||||
/// testing on main thread is counter productive.
|
||||
@globalActor
|
||||
actor TestsActor: GlobalActor {
|
||||
static var shared = TestsActor()
|
||||
|
||||
@@ -28,11 +28,13 @@ final class Lc0Tests: BaseEngineTests {
|
||||
|
||||
await startEngine(expectation: expectationStartEngine)
|
||||
await stopEngine(expectation: expectationStopEngine)
|
||||
//LC0 has an internal mutex failure "Unhandled exception: mutex lock failed: Invalid argument"
|
||||
//when trying to stop and start the engine too fast.
|
||||
//Adding this 100 ms delay circumvent that issue.
|
||||
//Once this issue is resolved, this override func
|
||||
//can be removed and use the EngineRestart test on BeseEngineTests
|
||||
|
||||
// lc0 has an internal mutex failure "Unhandled exception: mutex lock failed: Invalid argument"
|
||||
// when trying to stop and start the engine too fast.
|
||||
// This 100 ms delay circumvents the issue.
|
||||
//
|
||||
// Once this issue is resolved, this function
|
||||
// can be removed, using `BaseEngineTests.testEngineRestart()` instead.
|
||||
try? await Task.sleep(for: .milliseconds(100))
|
||||
await startEngine(expectation: expectationStartEngine)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user