The core-cpp migration (#805, #806) moved TimeoutScheduler, base64, the wakeup pipe and the per-model strands onto core-cpp, and added coroutines on core::async. This issue lists where morph and core-cpp still overlap, and what moving each would take against the code as it is now. Each item is a candidate for its own change, and none of them is required by the migration.
| morph |
core-cpp |
What moving would take |
morph::exec leaf executors: IExecutor, ThreadPoolExecutor, MainThreadExecutor, InlineExecutor, and qt::QtExecutor |
core::async::IExecutor, ThreadPoolExecutor, ResumeOn; core::net::EventLoop as an executor |
Blocked on contour-terminal/core-cpp#56. The per-model strands have already moved (exec::detail::ModelStrands over core::async::KeyedStrands; morph keeps the adapters CoreExecutorOver, TaskResumer, ExecutorResumer). What remains is the leaf executors, which post std::function<void()> (28 post() sites in include/morph, plus every Completion callback). core::async::IExecutor accepts only submit(coroutine_handle<>) and submit(ParkedWork) and has no way to run a closure. Replacing them therefore needs either a closure submit in core-cpp, or every closure wrapped in a coroutine frame (one allocation per post). MainThreadExecutor::runFor/drain and QtExecutor have no core-cpp counterpart. It is a public API change and needs docs/spec/core/executor.md first. |
morph::log |
core::log |
Proposed: keep. morph::log::logError is the sink for orphaned errors and for exceptions posted tasks throw, and tests silence it through --log-level. The two filter differently: morph gates on one ordered threshold (debug < info < warn < error < off); core::log enables or disables named Category objects individually and has no severity order. Moving needs a decision on how the threshold maps onto categories. morph does not link core::log today, and core-cpp's async, net and platform modules don't log through it. So no core-cpp output currently bypasses morph's sink, and moving would only gain a shared sink for applications that already use core::log. |
morph::core::FileIoOps (the injectable file-I/O seam for the journal and both offline queues) |
core::platform::FileSystem |
Blocked on contour-terminal/core-cpp#57. FileIoOps injects fopen, fwrite, fflush, fsync(FILE*), resizeFile and directory syncPath, and the torn-tail repair works on a FILE* (ftell, seek to end, truncate). FileSystem (v0.5.0) is path- and stream-based and has none of these: no file or directory sync, no truncate, no handle to sync. Moving needs a file-handle abstraction with write/flush/sync/truncate/tell and directory sync landed in core-cpp first, and a morph adapter until then. core::platform is also not linked under WebAssembly. |
| Four independent wall-clock sources (below) |
core::platform::IWallClock, injected (header already reachable: timeout_scheduler.hpp includes core/platform/Clock.hpp on every platform) |
Actionable now; the two raw sites are in #842. Reconciling them into one injected IWallClock: • DateTime::now() / Timestamp::now() with a process-global override. No caller in include/morph or src; used by examples/ledger and tests. • session::Clock (std::function<int64_t()>, default systemClockMs) for token issue/verify. Already injected, per call. • Model::recordIfAttached stamps action-log entries (LogEntry::timestampMs) from system_clock::now() directly. Not injectable. • SqliteOfflineQueue::nowMillis() stamps enqueue and update times from system_clock::now() directly. Not injectable. The last two are the ones a test cannot pin today, and they are the smallest step: give Model and SqliteOfflineQueue an IWallClock defaulting to SystemWallClock. |
morph::net (POSIX sockets only; MORPH_BUILD_NET warns and builds nothing on Windows) |
core::net sockets, listeners and TLS on every platform, IOCP on Windows |
Valid, not scheduled. SocketServer runs one accept thread and one thread per client; SocketBackend uses raw POSIX sockets (~1550 lines together). Porting onto core::net (IListener, TcpClient, EventLoop) would give the raw-socket transport Windows support, at the cost of rewriting thread-per-connection as event-loop flows. TLS would also need CORE_CPP_WITH_TLS, which morph builds OFF. |
Constraints any of these must keep
- morph's public surface stays header-only. core-cpp's static modules are built alongside it, as they are now, but nothing may require a consumer to link a morph library of its own.
- Single-threaded WebAssembly (Qt
wasm_singlethread, emsdk 3.1.56, no -pthread) must keep building and working. No thread, no blocking wait, and nothing newer than libc++ 17 without a feature-test macro. Of core-cpp, only its WebAssembly subset is available there.
- The wire protocol and the remote backends stay unchanged.
Verification status. Read from source, not built, measured or prototyped: morph at master 2cfbdd4 (core-cpp pinned at v0.5.0), and a checkout of core-cpp's v0.5.0 tag (async/IExecutor.hpp, async/ThreadPoolExecutor.hpp, platform/FileSystem.hpp, platform/Clock.hpp, platform/CMakeLists.txt, log/, net/). The clock sites come from grepping include/morph and src for system_clock::now, DateTime::now and Timestamp::now; a clock read through another spelling would be missed. That Clock.hpp compiles under WebAssembly is inferred from its unconditional include, not from a wasm build.
What would close this: each row either moved (with its own issue), or recorded here as deliberately kept, with the reason.
The core-cpp migration (#805, #806) moved
TimeoutScheduler, base64, the wakeup pipe and the per-model strands onto core-cpp, and added coroutines oncore::async. This issue lists where morph and core-cpp still overlap, and what moving each would take against the code as it is now. Each item is a candidate for its own change, and none of them is required by the migration.morph::execleaf executors:IExecutor,ThreadPoolExecutor,MainThreadExecutor,InlineExecutor, andqt::QtExecutorcore::async::IExecutor,ThreadPoolExecutor,ResumeOn;core::net::EventLoopas an executorexec::detail::ModelStrandsovercore::async::KeyedStrands; morph keeps the adaptersCoreExecutorOver,TaskResumer,ExecutorResumer). What remains is the leaf executors, which poststd::function<void()>(28post()sites ininclude/morph, plus everyCompletioncallback).core::async::IExecutoraccepts onlysubmit(coroutine_handle<>)andsubmit(ParkedWork)and has no way to run a closure. Replacing them therefore needs either a closuresubmitin core-cpp, or every closure wrapped in a coroutine frame (one allocation per post).MainThreadExecutor::runFor/drainandQtExecutorhave no core-cpp counterpart. It is a public API change and needsdocs/spec/core/executor.mdfirst.morph::logcore::logmorph::log::logErroris the sink for orphaned errors and for exceptions posted tasks throw, and tests silence it through--log-level. The two filter differently: morph gates on one ordered threshold (debug < info < warn < error < off);core::logenables or disables namedCategoryobjects individually and has no severity order. Moving needs a decision on how the threshold maps onto categories. morph does not linkcore::logtoday, and core-cpp'sasync,netandplatformmodules don't log through it. So no core-cpp output currently bypasses morph's sink, and moving would only gain a shared sink for applications that already usecore::log.morph::core::FileIoOps(the injectable file-I/O seam for the journal and both offline queues)core::platform::FileSystemFileIoOpsinjectsfopen,fwrite,fflush,fsync(FILE*),resizeFileand directorysyncPath, and the torn-tail repair works on aFILE*(ftell, seek to end, truncate).FileSystem(v0.5.0) is path- and stream-based and has none of these: no file or directory sync, no truncate, no handle to sync. Moving needs a file-handle abstraction with write/flush/sync/truncate/tell and directory sync landed in core-cpp first, and a morph adapter until then.core::platformis also not linked under WebAssembly.core::platform::IWallClock, injected (header already reachable:timeout_scheduler.hppincludescore/platform/Clock.hppon every platform)IWallClock:•
DateTime::now()/Timestamp::now()with a process-global override. No caller ininclude/morphorsrc; used byexamples/ledgerand tests.•
session::Clock(std::function<int64_t()>, defaultsystemClockMs) for token issue/verify. Already injected, per call.•
Model::recordIfAttachedstamps action-log entries (LogEntry::timestampMs) fromsystem_clock::now()directly. Not injectable.•
SqliteOfflineQueue::nowMillis()stamps enqueue and update times fromsystem_clock::now()directly. Not injectable.The last two are the ones a test cannot pin today, and they are the smallest step: give
ModelandSqliteOfflineQueueanIWallClockdefaulting toSystemWallClock.morph::net(POSIX sockets only;MORPH_BUILD_NETwarns and builds nothing on Windows)core::netsockets, listeners and TLS on every platform, IOCP on WindowsSocketServerruns one accept thread and one thread per client;SocketBackenduses raw POSIX sockets (~1550 lines together). Porting ontocore::net(IListener,TcpClient,EventLoop) would give the raw-socket transport Windows support, at the cost of rewriting thread-per-connection as event-loop flows. TLS would also needCORE_CPP_WITH_TLS, which morph buildsOFF.Constraints any of these must keep
wasm_singlethread, emsdk 3.1.56, no-pthread) must keep building and working. No thread, no blocking wait, and nothing newer than libc++ 17 without a feature-test macro. Of core-cpp, only its WebAssembly subset is available there.Verification status. Read from source, not built, measured or prototyped: morph at
master2cfbdd4 (core-cpp pinned at v0.5.0), and a checkout of core-cpp'sv0.5.0tag (async/IExecutor.hpp,async/ThreadPoolExecutor.hpp,platform/FileSystem.hpp,platform/Clock.hpp,platform/CMakeLists.txt,log/,net/). The clock sites come from greppinginclude/morphandsrcforsystem_clock::now,DateTime::nowandTimestamp::now; a clock read through another spelling would be missed. ThatClock.hppcompiles under WebAssembly is inferred from its unconditional include, not from a wasm build.What would close this: each row either moved (with its own issue), or recorded here as deliberately kept, with the reason.