Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/slic3r/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,12 @@ set(SLIC3R_GUI_SOURCES
GUI/Field.hpp
GUI/FileArchiveDialog.cpp
GUI/FileArchiveDialog.hpp
GUI/Flutter/FlutterChannel.cpp
GUI/Flutter/FlutterMiddleware.cpp
GUI/Flutter/FlutterPanel.cpp
GUI/Flutter/FlutterPanel.hpp
GUI/Flutter/flutter_host.h
GUI/Flutter/FlutterChannel.h
GUI/Flutter/flutter_platform.h
GUI/format.hpp
GUI/GCodeViewer.cpp
Expand Down Expand Up @@ -763,6 +766,7 @@ endif()

if(FLUTTER_DEPS_AVAILABLE)
target_compile_definitions(libslic3r_gui PRIVATE HAS_FLUTTER)
target_compile_definitions(libslic3r_gui PRIVATE $<$<CONFIG:Debug>:ORCA_FLUTTER_VERBOSE>)
endif()

if (SLIC3R_STATIC)
Expand Down
207 changes: 207 additions & 0 deletions src/slic3r/GUI/Flutter/FlutterChannel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
#include "FlutterChannel.h"
#include "FlutterMiddleware.h"
#include <wx/app.h>
#include <boost/log/trivial.hpp>

// ── FlutterChannel ────────────────────────────────────────────────

FlutterChannel::FlutterChannel(const std::string& name)
: m_channelName(name)
{
m_routes["system.ready"] = [this](const std::string&, FlutterViewHost::Reply) {
if (!m_view) {
BOOST_LOG_TRIVIAL(warning) << "[flutter] drain: view is null, dropping "
<< m_pendingMessages.size() << " messages";
m_pendingMessages.clear();
return;
}
m_dartReady = true;
while (!m_pendingMessages.empty()) {
auto& msg = m_pendingMessages.front();
try {
m_view->invokeMethod(msg.first, msg.second);
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "[flutter] drain failed for "
<< msg.first << ": " << e.what();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "[flutter] drain failed for "
<< msg.first;
}
m_pendingMessages.pop_front();
}
};
}

FlutterChannel& FlutterChannel::on(const std::string& method, Handler h) {
assert(!m_handlerCalled && "FlutterChannel::on: called after handler()");
assert(h);
if (method == "system.ready") {
BOOST_LOG_TRIVIAL(warning) << "[flutter] system.ready is reserved, ignoring user handler";
return *this;
}
assert(m_routes.find(method) == m_routes.end() &&
"FlutterChannel::on: duplicate method");
m_routes[method] = std::move(h);
return *this;
}

FlutterChannel& FlutterChannel::use(std::shared_ptr<Middleware> m) {
assert(m);
assert(!m_handlerCalled && "FlutterChannel::use: called after handler()");
if (!m_handlerCalled)
m_middleware.push_back(std::move(m));
return *this;
}

FlutterViewHost::MethodCallHandler
FlutterChannel::handler(FlutterViewHost* view) {
assert(view);
assert(!m_handlerCalled && "FlutterChannel::handler: already called");
m_view = view;
m_handlerCalled = true;

auto routes = m_routes;
auto middleware = std::make_shared<std::vector<std::shared_ptr<Middleware>>>(
m_middleware);

auto replied = std::make_shared<bool>(false);
return [routes = std::move(routes), middleware, replied](
const std::string& method, const std::string& args,
FlutterViewHost::Reply rawReply) {

FlutterViewHost::Reply reply = [replied, rawReply = std::move(rawReply)]
(const std::string& r) {
if (*replied) return;
*replied = true;
rawReply(r);
};

// onInbound (forward)
auto& mws = *middleware;

auto executeChain = [&](const std::string& m, const std::string& a,
FlutterViewHost::Reply r, size_t startIdx = 0) {
for (size_t i = startIdx; i < mws.size(); ++i) {
try {
if (!mws[i]->onInbound(m, a, r)) {
for (int j = static_cast<int>(i) - 1; j >= 0; --j)
mws[j]->onOutbound(m, a);
return;
}
} catch (const std::exception& e) {
r(std::string("Error: ") + e.what());
for (int j = static_cast<int>(i) - 1; j >= 0; --j)
mws[j]->onOutbound(m, a);
return;
} catch (...) {
r("Error: middleware exception");
for (int j = static_cast<int>(i) - 1; j >= 0; --j)
mws[j]->onOutbound(m, a);
return;
}
}
auto it = routes.find(m);
if (it != routes.end()) {
try { it->second(a, r); }
catch (const std::exception& e) { r(std::string("Error: ") + e.what()); }
catch (...) { r("Error: unknown"); }
} else {
r("");
}
for (int i = static_cast<int>(mws.size()) - 1; i >= 0; --i)
mws[i]->onOutbound(m, a);
};

for (size_t i = 0; i < mws.size(); ++i) {
bool pass = false;
try {
pass = mws[i]->onInbound(method, args, reply);
} catch (const std::exception& e) {
reply(std::string("Error: ") + e.what());
for (int j = static_cast<int>(i) - 1; j >= 0; --j)
mws[j]->onOutbound(method, args);
return;
} catch (...) {
reply("Error: middleware exception");
for (int j = static_cast<int>(i) - 1; j >= 0; --j)
mws[j]->onOutbound(method, args);
return;
}
if (!pass) {
// ThreadGuardMiddleware defers execution to UI thread via CallAfter.
// dynamic_pointer_cast is used intentionally instead of a virtual method
// on the Middleware base class to keep the base interface minimal.
if (auto guard = std::dynamic_pointer_cast<ThreadGuardMiddleware>(mws[i])) {
guard->CallAfter([executeChain, method, args, reply, i]() {
executeChain(method, args, reply, i);
});
return;
}
for (int j = static_cast<int>(i) - 1; j >= 0; --j)
mws[j]->onOutbound(method, args);
return;
}
}

// All middlewares passed — run handler + onOutbound
auto it = routes.find(method);
if (it != routes.end()) {
try { it->second(args, reply); }
catch (const std::exception& e) { reply(std::string("Error: ") + e.what()); }
catch (...) { reply("Error: unknown"); }
} else {
reply("");
}
for (int i = static_cast<int>(mws.size()) - 1; i >= 0; --i)
mws[i]->onOutbound(method, args);
};
}

void FlutterChannel::invoke(const std::string& method, const std::string& args)
{
if (!wxThread::IsMain()) {
BOOST_LOG_TRIVIAL(warning)
<< "[flutter] invoke(" << method << ") called from non-UI thread, dropping";
return;
}
if (!m_dartReady) {
if (m_pendingMessages.size() >= 256) {
BOOST_LOG_TRIVIAL(warning) << "[flutter] queue full, dropping oldest message";
m_pendingMessages.pop_front();
}
m_pendingMessages.emplace_back(method, args);
return;
}
if (m_view) m_view->invokeMethod(method, args);
}

FlutterChannel::Namespace FlutterChannel::ns(const std::string& prefix) {
return Namespace(this, prefix);
}

// ── Namespace ─────────────────────────────────────────────────────

FlutterChannel::Namespace::Namespace(FlutterChannel* parent,
std::string prefix)
: m_parent(parent), m_prefix(std::move(prefix)) {}

FlutterChannel::Namespace&
FlutterChannel::Namespace::on(const std::string& method, Handler h) {
m_parent->on(m_prefix + "." + method, std::move(h));
return *this;
}

void FlutterChannel::Namespace::invoke(const std::string& method,
const std::string& args) {
m_parent->invoke(m_prefix + "." + method, args);
}

// ── Middleware defaults ───────────────────────────────────────────

bool FlutterChannel::Middleware::onInbound(const std::string&,
const std::string&, Reply) {
return true;
}

void FlutterChannel::Middleware::onOutbound(const std::string&,
const std::string&) {}
72 changes: 72 additions & 0 deletions src/slic3r/GUI/Flutter/FlutterChannel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#pragma once
#include "flutter_host.h"
#include <string>
#include <functional>
#include <memory>
#include <deque>
#include <unordered_map>
#include <utility>
#include <vector>
#include <cassert>

class FlutterChannel {
public:
using Handler = std::function<void(const std::string&, FlutterViewHost::Reply)>;
using Reply = FlutterViewHost::Reply;
using MiddlewarePtr = std::shared_ptr<class Middleware>;

explicit FlutterChannel(const std::string& name);
FlutterChannel(const FlutterChannel&) = delete;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

显式删除了拷贝构造/赋值,但没有声明移动构造/赋值,建议显式加上移动构造

FlutterChannel& operator=(const FlutterChannel&) = delete;

// Handler registration (must precede handler(view))
FlutterChannel& on(const std::string& method, Handler h);

// Namespace — prefix.method routing
class Namespace {
public:
Namespace(const Namespace&) = delete;
Namespace& operator=(const Namespace&) = delete;
Namespace& on(const std::string& method, Handler h);
void invoke(const std::string& method, const std::string& args);
private:
friend class FlutterChannel;
Namespace(FlutterChannel* parent, std::string prefix);
FlutterChannel* m_parent;
std::string m_prefix;
};
Namespace ns(const std::string& prefix);

// Middleware pipeline
class Middleware {
public:
virtual ~Middleware() = default;
virtual bool onInbound(const std::string& method,
const std::string& args, Reply reply);
virtual void onOutbound(const std::string& method,
const std::string& args);
};
FlutterChannel& use(std::shared_ptr<Middleware> m);

// View binding (one-step, cannot call twice)
FlutterViewHost::MethodCallHandler handler(FlutterViewHost* view);

// C++ → Dart (fire-and-forget)
void invoke(const std::string& method, const std::string& args);

const std::string& name() const { return m_channelName; }

private:
std::string m_channelName;
std::unordered_map<std::string, Handler> m_routes;
std::vector<std::shared_ptr<Middleware>> m_middleware;
FlutterViewHost* m_view = nullptr; // main-thread only
bool m_handlerCalled = false;
bool m_dartReady = false;
std::deque<std::pair<std::string, std::string>> m_pendingMessages;
};

// Middleware factory functions
std::shared_ptr<FlutterChannel::Middleware> makeLoggingMiddleware();
std::shared_ptr<FlutterChannel::Middleware> makeThreadGuardMiddleware();
std::shared_ptr<FlutterChannel::Middleware> makeTimeoutMiddleware(int seconds);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议用 makeTimeoutMiddleware (std::chrono::seconds seconds)

89 changes: 89 additions & 0 deletions src/slic3r/GUI/Flutter/FlutterMiddleware.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#include "FlutterChannel.h"
#include "FlutterMiddleware.h"
#include <algorithm>
#include <wx/app.h>
#include <wx/timer.h>
#include <boost/log/trivial.hpp>

// ── LoggingMiddleware ─────────────────────────────────────────

class LoggingMiddleware : public FlutterChannel::Middleware {
uint64_t m_nextId = 0;
bool onInbound(const std::string& method, const std::string& args,
FlutterViewHost::Reply) override {
uint64_t id = ++m_nextId;
#ifdef ORCA_FLUTTER_VERBOSE
BOOST_LOG_TRIVIAL(trace) << "[flutter][" << id << "] " << method << " " << args;
#endif
return true;
}
void onOutbound(const std::string& method,
const std::string& args) override {
#ifdef ORCA_FLUTTER_VERBOSE
BOOST_LOG_TRIVIAL(trace) << "[flutter][" << m_nextId << "] out " << method << " " << args;
#endif
}
};

// ── ThreadGuardMiddleware ─────────────────────────────────────

bool ThreadGuardMiddleware::onInbound(const std::string&, const std::string&,
FlutterViewHost::Reply) {
if (!wxThread::IsMain()) {
wxASSERT(wxThread::IsMain());
return false;
}
return true;
}

// ── TimeoutMiddleware ─────────────────────────────────────────

class TimeoutMiddleware : public FlutterChannel::Middleware,
public wxEvtHandler {
int m_sec;
public:
explicit TimeoutMiddleware(int s) : m_sec(s) {}
~TimeoutMiddleware() {
for (auto& t : m_timers) {
if (t->IsRunning()) t->Stop();
}
}

bool onInbound(const std::string&, const std::string&,
FlutterViewHost::Reply reply) override {
// Clean up fired one-shot timers
m_timers.erase(
std::remove_if(m_timers.begin(), m_timers.end(),
[](const auto& t) { return !t->IsRunning(); }),
m_timers.end());
auto fn = std::make_shared<FlutterViewHost::Reply>(std::move(reply));
auto fired = std::make_shared<bool>(false);
auto t = std::make_unique<wxTimer>(this);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handler 正常返回后主动取消对应的 timer 即可,这样写 m_timers 会无限增长

int sec = m_sec;
t->Bind(wxEVT_TIMER, [fired, fn, sec](wxTimerEvent&) {
if (!*fired) {
*fired = true;
(*fn)("{\"code\":\"TIMEOUT\","
"\"message\":\"Handler timed out after " +
std::to_string(sec) + "s\"}");
}
});
t->Start(sec * 1000, true);
m_timers.push_back(std::move(t));
return true;
}

private:
std::vector<std::unique_ptr<wxTimer>> m_timers;
};

// ── Factories ─────────────────────────────────────────────────

std::shared_ptr<FlutterChannel::Middleware> makeLoggingMiddleware()
{ return std::make_shared<LoggingMiddleware>(); }

std::shared_ptr<FlutterChannel::Middleware> makeThreadGuardMiddleware()
{ return std::make_shared<ThreadGuardMiddleware>(); }

std::shared_ptr<FlutterChannel::Middleware> makeTimeoutMiddleware(int s)
{ return std::make_shared<TimeoutMiddleware>(s); }
Loading