加上纯色websocket
This commit is contained in:
@@ -59,31 +59,23 @@ bool Frame_Pacing_Policy::accept_periodic_tick(double time_milliseconds) {
|
||||
return false;
|
||||
if (skip_next_periodic_.exchange(false, std::memory_order_acq_rel))
|
||||
return false;
|
||||
return !in_flight_.load(std::memory_order_acquire);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Frame_Pacing_Policy::request_immediate() {
|
||||
const auto value = read();
|
||||
if (!value.render_enabled) return false;
|
||||
/* immediate 与下一次周期机会合并,真正的 busy/latest 语义由 Plot 管理。 */
|
||||
skip_next_periodic_.store(true, std::memory_order_release);
|
||||
if (in_flight_.load(std::memory_order_acquire)) {
|
||||
urgent_pending_.store(true, std::memory_order_release);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Frame_Pacing_Policy::frame_submitted() {
|
||||
in_flight_.store(true, std::memory_order_release);
|
||||
}
|
||||
void Frame_Pacing_Policy::frame_submitted() {}
|
||||
|
||||
bool Frame_Pacing_Policy::frame_completed() {
|
||||
in_flight_.store(false, std::memory_order_release);
|
||||
return urgent_pending_.exchange(false, std::memory_order_acq_rel);
|
||||
return false;
|
||||
}
|
||||
|
||||
void Frame_Pacing_Policy::frame_rejected() {
|
||||
in_flight_.store(false, std::memory_order_release);
|
||||
}
|
||||
void Frame_Pacing_Policy::frame_rejected() {}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ struct Frame_Pacing_Properties {
|
||||
bool render_enabled{true};
|
||||
bool video_enabled{true};
|
||||
Frame_Pacing_Mode mode{Frame_Pacing_Mode::fixed_rate};
|
||||
double fixed_rate_fps{100.0};
|
||||
double fixed_rate_fps{30.0};
|
||||
double maximum_rate_fps{100.0};
|
||||
};
|
||||
|
||||
@@ -36,8 +36,8 @@ struct Frame_Pacing_Policy final {
|
||||
[[nodiscard]] double scheduled_rate_fps() const;
|
||||
|
||||
/*
|
||||
* 周期 tick 是否允许生成新帧。immediate 会提前生成一帧并消费下一次周期机会。
|
||||
* in_flight 只表达 Scene 是否已有一帧尚未完成,不改变 Scene::render(Frame*) 抽象。
|
||||
* 周期 tick 只判断策略/周期语义,不再承担 Scene in-flight 门控。
|
||||
* 实际渲染准入由调用方在 Scene::advance 前控制;忙时保留 latest pending tick。
|
||||
*/
|
||||
[[nodiscard]] bool accept_periodic_tick(double time_milliseconds);
|
||||
[[nodiscard]] bool request_immediate();
|
||||
@@ -49,9 +49,7 @@ private:
|
||||
template <typename Edit> void update(Edit&& edit);
|
||||
|
||||
std::atomic<std::shared_ptr<const Frame_Pacing_Properties>> properties_;
|
||||
std::atomic_bool in_flight_{};
|
||||
std::atomic_bool skip_next_periodic_{};
|
||||
std::atomic_bool urgent_pending_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -137,7 +137,17 @@ struct Frame_Scheduler::Private {
|
||||
if (!state->alive.load(std::memory_order_acquire)) break;
|
||||
state->mode = Timer::State::Mode::periodic;
|
||||
state->period = command.value;
|
||||
state->next_deadline = now + state->period;
|
||||
/* 同频 Plot 不要永久锁在同一 deadline 上。稳定 timer id 给
|
||||
* 每个周期分配固定 phase;时间值仍来自同一全局 origin。 */
|
||||
{
|
||||
constexpr std::uint64_t phase_slots = 8;
|
||||
const auto phase_slot = (state->id - 1U) % phase_slots;
|
||||
const auto phase = Nanoseconds{
|
||||
state->period.count() *
|
||||
static_cast<std::int64_t>(phase_slot) /
|
||||
static_cast<std::int64_t>(phase_slots)};
|
||||
state->next_deadline = now + state->period + phase;
|
||||
}
|
||||
schedule_state(*state, now);
|
||||
break;
|
||||
case Command_Type::once:
|
||||
@@ -169,13 +179,14 @@ struct Frame_Scheduler::Private {
|
||||
|
||||
void fire(Timer::State& state) noexcept {
|
||||
if (!state.alive.load(std::memory_order_acquire)) return;
|
||||
const auto now = Scheduler_Clock::now();
|
||||
const auto issued_at = Scheduler_Clock::now();
|
||||
const ::Tick sequence = wheel.now();
|
||||
try {
|
||||
state.handler(Frame_Scheduler::Tick{
|
||||
now,
|
||||
issued_at,
|
||||
static_cast<std::uint64_t>(sequence),
|
||||
std::chrono::duration<double, std::milli>(now - origin).count()});
|
||||
std::chrono::duration<double, std::milli>(
|
||||
issued_at - origin).count()});
|
||||
}
|
||||
catch (...) {
|
||||
/* Timer callback 是控制面入口,不能因为业务异常终止全局时钟。 */
|
||||
@@ -189,12 +200,16 @@ struct Frame_Scheduler::Private {
|
||||
state.period <= Nanoseconds::zero())
|
||||
return;
|
||||
|
||||
/* callback 自身也可能消耗控制线程时间;重取 now,避免用触发前时间
|
||||
* 重排 deadline 导致已经过期的周期再次被排入 timer wheel。 */
|
||||
const auto reschedule_now = Scheduler_Clock::now();
|
||||
state.next_deadline += state.period;
|
||||
if (state.next_deadline <= now) {
|
||||
const auto missed = (now - state.next_deadline) / state.period + 1;
|
||||
if (state.next_deadline <= reschedule_now) {
|
||||
const auto missed =
|
||||
(reschedule_now - state.next_deadline) / state.period + 1;
|
||||
state.next_deadline += state.period * missed;
|
||||
}
|
||||
schedule_state(state, now);
|
||||
schedule_state(state, reschedule_now);
|
||||
}
|
||||
|
||||
void advance_to(Scheduler_Clock::time_point now) {
|
||||
|
||||
@@ -156,8 +156,8 @@ private:
|
||||
std::vector<std::uint64_t> worker_cpu_starts;
|
||||
std::unique_ptr<Worker_Statistics[]> worker_statistics;
|
||||
std::size_t worker_statistics_count{};
|
||||
std::atomic<Render_Frame*> trace_frame{};
|
||||
std::shared_mutex trace_mutex{}; /* 仅按需捕获时保护 Frame* 获取与关闭。 */
|
||||
std::unordered_map<std::uint64_t, Render_Frame*> trace_tasks{};
|
||||
std::shared_mutex trace_mutex{}; /* 按原生节点身份把并发 Taskflow 归属到各自 Frame。 */
|
||||
std::uint64_t worker_occupation_limit_ns{}; /* 单节点连续非 CPU 等待 Worker 的上限。 */
|
||||
Task_Overrun_Action worker_overrun_action{}; /* 节点超过占用上限后的处置策略。 */
|
||||
std::atomic_bool watchdog_stopping{}; /* Watchdog 生命周期停止标志。 */
|
||||
@@ -433,22 +433,20 @@ public:
|
||||
record.native_id = static_cast<std::uint64_t>(task.hash_value());
|
||||
record.type = task.type();
|
||||
worker_starts.push_back(std::move(record));
|
||||
auto* frame = trace_frame.load(std::memory_order_acquire);
|
||||
Render_Frame* frame{};
|
||||
/*
|
||||
* 帧租约从 on_entry 持续到对应 on_exit。只在退出时登记写入者会留下
|
||||
* “捕获已关闭、物理帧已复用、迟到 on_exit 仍访问旧 Frame*”的窗口。
|
||||
* 每次被追踪的 Taskflow run 在提交前登记 native node -> Frame。
|
||||
* 因而 Render(N+1) 与外接 H264(N) 可以同时被 Observer 正确归属,
|
||||
* 不再使用“全局唯一当前 Frame”的假设。帧租约从 on_entry 持续到
|
||||
* on_exit,避免 graph 退役后迟到的 Observer 写入访问已复用 Frame。
|
||||
*/
|
||||
if (frame) {
|
||||
{
|
||||
std::shared_lock trace_guard(trace_mutex);
|
||||
frame = trace_frame.load(std::memory_order_acquire);
|
||||
if (frame && detail::Taskflow_Frame_Access::acquire_writer(*frame)) {
|
||||
if (!detail::Taskflow_Frame_Access::contains_task(
|
||||
*frame, static_cast<std::uint64_t>(task.hash_value()))) {
|
||||
detail::Taskflow_Frame_Access::release_writer(*frame);
|
||||
frame = nullptr;
|
||||
}
|
||||
}
|
||||
else frame = nullptr;
|
||||
const auto found = trace_tasks.find(
|
||||
static_cast<std::uint64_t>(task.hash_value()));
|
||||
if (found != trace_tasks.end() &&
|
||||
detail::Taskflow_Frame_Access::acquire_writer(*found->second))
|
||||
frame = found->second;
|
||||
}
|
||||
worker_starts.back().frame = frame;
|
||||
const auto active_depth = worker_state.active_depth.fetch_add(
|
||||
@@ -645,17 +643,44 @@ public:
|
||||
update_max(worker_state.last_task_time_ns, clock_ns(completed));
|
||||
}
|
||||
bool begin_trace(Render_Frame& frame, std::size_t workers) {
|
||||
std::unique_lock guard(trace_mutex);
|
||||
const auto current = trace_frame.load(std::memory_order_acquire);
|
||||
if (current) return current == &frame;
|
||||
/* Frame 自己保存捕获缓冲;多个 Frame 可以同时处于捕获窗口。 */
|
||||
detail::Taskflow_Frame_Access::begin_capture(frame, workers);
|
||||
trace_frame.store(&frame, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
void finish_trace(Render_Frame& frame) noexcept {
|
||||
void activate_graph(Render_Frame& frame, Task_Graph& graph) {
|
||||
if (!frame.taskflow_trace_requested()) return;
|
||||
const auto nodes = detail::Task_Graph_Access::nodes(graph);
|
||||
std::unique_lock guard(trace_mutex);
|
||||
if (trace_frame.load(std::memory_order_acquire) != &frame) return;
|
||||
trace_frame.store(nullptr, std::memory_order_release);
|
||||
for (const auto& node : nodes) {
|
||||
const auto [found, inserted] = trace_tasks.emplace(
|
||||
node.native_id, &frame);
|
||||
if (!inserted && found->second != &frame)
|
||||
throw std::logic_error(
|
||||
"concurrent traced runs share the same Taskflow node");
|
||||
}
|
||||
}
|
||||
void deactivate_graph(Render_Frame& frame, Task_Graph& graph) noexcept {
|
||||
if (!frame.taskflow_trace_requested()) return;
|
||||
try {
|
||||
const auto nodes = detail::Task_Graph_Access::nodes(graph);
|
||||
std::unique_lock guard(trace_mutex);
|
||||
for (const auto& node : nodes) {
|
||||
const auto found = trace_tasks.find(node.native_id);
|
||||
if (found != trace_tasks.end() && found->second == &frame)
|
||||
trace_tasks.erase(found);
|
||||
}
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
void finish_trace(Render_Frame& frame) noexcept {
|
||||
try {
|
||||
std::unique_lock guard(trace_mutex);
|
||||
for (auto it = trace_tasks.begin(); it != trace_tasks.end();) {
|
||||
if (it->second == &frame) it = trace_tasks.erase(it);
|
||||
else ++it;
|
||||
}
|
||||
}
|
||||
catch (...) {}
|
||||
detail::Taskflow_Frame_Access::finish_capture(frame);
|
||||
}
|
||||
void write_state(Task_Runtime_State& state, std::size_t workers, std::size_t active_topologies) const {
|
||||
@@ -929,12 +954,15 @@ public:
|
||||
std::string_view stage) {
|
||||
const auto token = detail::Taskflow_Frame_Access::begin_graph(
|
||||
frame, taskflow, stage);
|
||||
observer->activate_graph(frame, taskflow);
|
||||
try {
|
||||
const auto elapsed = run(taskflow);
|
||||
observer->deactivate_graph(frame, taskflow);
|
||||
detail::Taskflow_Frame_Access::finish_graph(token);
|
||||
return elapsed;
|
||||
}
|
||||
catch (...) {
|
||||
observer->deactivate_graph(frame, taskflow);
|
||||
detail::Taskflow_Frame_Access::finish_graph(token);
|
||||
throw;
|
||||
}
|
||||
@@ -943,10 +971,20 @@ public:
|
||||
std::function<void()> completion) {
|
||||
const auto token = detail::Taskflow_Frame_Access::begin_graph(
|
||||
frame, taskflow, stage);
|
||||
run(taskflow, [token, completion = std::move(completion)]() mutable {
|
||||
try {
|
||||
observer->activate_graph(frame, taskflow);
|
||||
run(taskflow, [this, &taskflow, &frame, token,
|
||||
completion = std::move(completion)]() mutable {
|
||||
observer->deactivate_graph(frame, taskflow);
|
||||
detail::Taskflow_Frame_Access::finish_graph(token);
|
||||
completion();
|
||||
});
|
||||
}
|
||||
catch (...) {
|
||||
observer->deactivate_graph(frame, taskflow);
|
||||
detail::Taskflow_Frame_Access::finish_graph(token);
|
||||
completion();
|
||||
});
|
||||
throw;
|
||||
}
|
||||
}
|
||||
void schedule(std::string name, std::function<void()> task) {
|
||||
if (!task) throw std::invalid_argument("Taskflow scheduled task is empty");
|
||||
|
||||
Reference in New Issue
Block a user