mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-08 01:00:05 +00:00
LibCore: Add swift bindings for EventLoop as an Executor and Actor
This commit is contained in:
parent
3af63bc5a3
commit
c8787e6a9f
Notes:
github-actions[bot]
2025-03-16 03:52:13 +00:00
Author: https://github.com/ADKaster
Commit: c8787e6a9f
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3880
10 changed files with 224 additions and 1 deletions
|
@ -114,3 +114,20 @@ endif()
|
|||
if (ANDROID)
|
||||
target_link_libraries(LibCore PRIVATE log)
|
||||
endif()
|
||||
|
||||
if (ENABLE_SWIFT)
|
||||
set(SWIFT_EXCLUDE_HEADERS "SocketAddressWindows.h")
|
||||
if(WIN32)
|
||||
list(APPEND SWIFT_EXCLUDE_HEADERS "EventLoopImplementationUnix.h")
|
||||
else()
|
||||
list(APPEND SWIFT_EXCLUDE_HEADERS "EventLoopImplementationWindows.h")
|
||||
endif()
|
||||
|
||||
generate_clang_module_map(LibCore EXCLUDE_FILES ${SWIFT_EXCLUDE_HEADERS})
|
||||
target_sources(LibCore PRIVATE
|
||||
EventSwift.mm
|
||||
EventLoopExecutor.swift)
|
||||
set_source_files_properties(EventSwift.mm PRIVATE PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
||||
target_link_libraries(LibCore PRIVATE AK)
|
||||
add_swift_target_properties(LibCore LAGOM_LIBRARIES AK)
|
||||
endif()
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <AK/Function.h>
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/Swift.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibCore/Event.h>
|
||||
#include <LibCore/Forward.h>
|
||||
|
@ -40,6 +41,10 @@ class ThreadEventQueue;
|
|||
// - Quit events, i.e. the event loop should exit.
|
||||
// Any event that the event loop needs to wait on or needs to repeatedly handle is stored in a handle, e.g. s_timers.
|
||||
class EventLoop {
|
||||
AK_MAKE_NONMOVABLE(EventLoop);
|
||||
AK_MAKE_NONCOPYABLE(EventLoop);
|
||||
|
||||
private:
|
||||
friend struct EventLoopPusher;
|
||||
|
||||
public:
|
||||
|
@ -90,7 +95,7 @@ public:
|
|||
|
||||
private:
|
||||
NonnullOwnPtr<EventLoopImplementation> m_impl;
|
||||
};
|
||||
} SWIFT_UNSAFE_REFERENCE;
|
||||
|
||||
void deferred_invoke(ESCAPING Function<void()>);
|
||||
|
||||
|
|
53
Libraries/LibCore/EventLoopExecutor.swift
Normal file
53
Libraries/LibCore/EventLoopExecutor.swift
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andrew Kaster <andrew@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
import AK
|
||||
@_exported import CoreCxx
|
||||
|
||||
extension Core.EventLoop: Equatable {
|
||||
func deferred_invoke(_ task: @escaping () -> Void) {
|
||||
Core.deferred_invoke_block(self, task)
|
||||
}
|
||||
|
||||
public static func == (lhs: Core.EventLoop, rhs: Core.EventLoop) -> Bool {
|
||||
Unmanaged.passUnretained(lhs).toOpaque() == Unmanaged.passUnretained(rhs).toOpaque()
|
||||
}
|
||||
}
|
||||
|
||||
public class EventLoopExecutor: SerialExecutor, TaskExecutor, @unchecked Sendable {
|
||||
nonisolated private let eventLoop: Core.EventLoop
|
||||
|
||||
public init() {
|
||||
eventLoop = Core.EventLoop.current()
|
||||
}
|
||||
|
||||
public init(eventLoop: Core.EventLoop) {
|
||||
self.eventLoop = eventLoop
|
||||
}
|
||||
|
||||
public nonisolated func enqueue(_ job: consuming ExecutorJob) {
|
||||
let job = UnownedJob(job)
|
||||
eventLoop.deferred_invoke { [self, job] in
|
||||
job.runSynchronously(
|
||||
isolatedTo: self.asUnownedSerialExecutor(),
|
||||
taskExecutor: self.asUnownedTaskExecutor())
|
||||
}
|
||||
}
|
||||
|
||||
public func checkIsolated() {
|
||||
precondition(Core.EventLoop.current() == eventLoop)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol EventLoopActor: Actor {
|
||||
nonisolated var executor: EventLoopExecutor { get } // impl with a let
|
||||
}
|
||||
|
||||
extension EventLoopActor {
|
||||
public nonisolated var unownedExecutor: UnownedSerialExecutor {
|
||||
executor.asUnownedSerialExecutor()
|
||||
}
|
||||
}
|
15
Libraries/LibCore/EventSwift.h
Normal file
15
Libraries/LibCore/EventSwift.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andrew Kaster <andrew@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibCore/Forward.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
void deferred_invoke_block(EventLoop& event_loop, void (^invokee)(void));
|
||||
|
||||
}
|
22
Libraries/LibCore/EventSwift.mm
Normal file
22
Libraries/LibCore/EventSwift.mm
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andrew Kaster <andrew@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/EventSwift.h>
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
# error "This file requires ARC"
|
||||
#endif
|
||||
|
||||
namespace Core {
|
||||
void deferred_invoke_block(EventLoop& event_loop, void (^invokee)(void))
|
||||
{
|
||||
event_loop.deferred_invoke([invokee = move(invokee)] {
|
||||
invokee();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -554,6 +554,24 @@ if (BUILD_TESTING)
|
|||
|
||||
lagom_test(../../Tests/LibCore/TestLibCoreDateTime.cpp LIBS LibUnicode)
|
||||
|
||||
if (ENABLE_SWIFT)
|
||||
find_package(SwiftTesting REQUIRED)
|
||||
|
||||
add_executable(TestCoreSwift
|
||||
../../Tests/LibCore/TestEventLoopActor.swift
|
||||
../../Tests/LibCore/TestEventLoop.cpp
|
||||
)
|
||||
|
||||
# FIXME: Swift doesn't seem to like object libraries for @main
|
||||
target_sources(TestCoreSwift PRIVATE ../../Tests/Resources/SwiftTestMain.swift)
|
||||
|
||||
set_target_properties(TestCoreSwift PROPERTIES SUFFIX .swift-testing)
|
||||
target_include_directories(TestCoreSwift PRIVATE ../../Tests/LibCore)
|
||||
target_link_libraries(TestCoreSwift PRIVATE AK LibCore SwiftTesting::SwiftTesting)
|
||||
add_test(NAME TestCoreSwift COMMAND TestCoreSwift)
|
||||
endif()
|
||||
|
||||
|
||||
# RegexLibC test POSIX <regex.h> and contains many Serenity extensions
|
||||
# It is therefore not reasonable to run it on Lagom, and we only run the Regex test
|
||||
lagom_test(../../Tests/LibRegex/Regex.cpp LIBS LibRegex WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../Tests/LibRegex)
|
||||
|
|
16
Tests/LibCore/TestEventLoop.cpp
Normal file
16
Tests/LibCore/TestEventLoop.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andrew Kaster <andrew@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "TestEventLoop.h"
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
|
||||
void install_thread_local_event_loop()
|
||||
{
|
||||
thread_local OwnPtr<Core::EventLoop> s_thread_local_event_loop = nullptr;
|
||||
if (!s_thread_local_event_loop)
|
||||
s_thread_local_event_loop = make<Core::EventLoop>();
|
||||
}
|
9
Tests/LibCore/TestEventLoop.h
Normal file
9
Tests/LibCore/TestEventLoop.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andrew Kaster <andrew@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
void install_thread_local_event_loop();
|
63
Tests/LibCore/TestEventLoopActor.swift
Normal file
63
Tests/LibCore/TestEventLoopActor.swift
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andrew Kaster <andrew@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
import AK
|
||||
import Core
|
||||
import CoreTesting
|
||||
import Testing
|
||||
|
||||
actor TestEventLoopActor: EventLoopActor {
|
||||
nonisolated public let executor: EventLoopExecutor
|
||||
|
||||
init() {
|
||||
install_thread_local_event_loop()
|
||||
executor = EventLoopExecutor()
|
||||
}
|
||||
|
||||
nonisolated func submit(action: @escaping @Sendable () async -> Void) {
|
||||
Task(executorPreference: self.executor) {
|
||||
await action()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suite
|
||||
struct TestEventLoop {
|
||||
@Test
|
||||
func testEventLoopActor() async {
|
||||
// Creates an executor around EventLoop::current()
|
||||
let actor = TestEventLoopActor()
|
||||
|
||||
let ev = Core.EventLoop.current()
|
||||
print("Event loop at \(Unmanaged.passUnretained(ev).toOpaque())")
|
||||
|
||||
let (stream, continuation) = AsyncStream<Int>.makeStream()
|
||||
var iterator = stream.makeAsyncIterator()
|
||||
|
||||
actor.submit {
|
||||
#expect(ev == Core.EventLoop.current(), "Closure is executed on event loop")
|
||||
print("Hello from event loop at \(Unmanaged.passUnretained(Core.EventLoop.current()).toOpaque())")
|
||||
|
||||
continuation.yield(42)
|
||||
}
|
||||
|
||||
actor.submit {
|
||||
#expect(ev == Core.EventLoop.current(), "Closure is executed on event loop")
|
||||
Core.EventLoop.current().quit(4)
|
||||
|
||||
continuation.yield(1234)
|
||||
continuation.finish()
|
||||
}
|
||||
|
||||
let rc = ev.exec()
|
||||
#expect(rc == 4)
|
||||
// Values not available until event loop has processed tasks
|
||||
#expect(await iterator.next() == 42)
|
||||
#expect(await iterator.next() == 1234)
|
||||
|
||||
#expect(ev == Core.EventLoop.current(), "Event loop exists until end of function")
|
||||
}
|
||||
}
|
5
Tests/LibCore/module.modulemap
Normal file
5
Tests/LibCore/module.modulemap
Normal file
|
@ -0,0 +1,5 @@
|
|||
module CoreTesting {
|
||||
header "TestEventLoop.h"
|
||||
requires cplusplus
|
||||
export *
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue