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)
|
if (ANDROID)
|
||||||
target_link_libraries(LibCore PRIVATE log)
|
target_link_libraries(LibCore PRIVATE log)
|
||||||
endif()
|
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/Function.h>
|
||||||
#include <AK/Noncopyable.h>
|
#include <AK/Noncopyable.h>
|
||||||
#include <AK/NonnullOwnPtr.h>
|
#include <AK/NonnullOwnPtr.h>
|
||||||
|
#include <AK/Swift.h>
|
||||||
#include <AK/Time.h>
|
#include <AK/Time.h>
|
||||||
#include <LibCore/Event.h>
|
#include <LibCore/Event.h>
|
||||||
#include <LibCore/Forward.h>
|
#include <LibCore/Forward.h>
|
||||||
|
@ -40,6 +41,10 @@ class ThreadEventQueue;
|
||||||
// - Quit events, i.e. the event loop should exit.
|
// - 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.
|
// 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 {
|
class EventLoop {
|
||||||
|
AK_MAKE_NONMOVABLE(EventLoop);
|
||||||
|
AK_MAKE_NONCOPYABLE(EventLoop);
|
||||||
|
|
||||||
|
private:
|
||||||
friend struct EventLoopPusher;
|
friend struct EventLoopPusher;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -90,7 +95,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NonnullOwnPtr<EventLoopImplementation> m_impl;
|
NonnullOwnPtr<EventLoopImplementation> m_impl;
|
||||||
};
|
} SWIFT_UNSAFE_REFERENCE;
|
||||||
|
|
||||||
void deferred_invoke(ESCAPING Function<void()>);
|
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)
|
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
|
# 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
|
# 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)
|
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